package com.netease.nimlib.lucene.index;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.SortField.Type;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.grouping.GroupDocs;
import org.apache.lucene.search.grouping.GroupingSearch;
import org.apache.lucene.search.grouping.TopGroups;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.BytesRef;
import org.lukhnos.portmobile.file.Paths;
import org.wltea.analyzer.lucene.IKAnalyzer;

import android.util.Log;

import com.netease.nimlib.fts.query.NIMTokenizer;
import com.netease.nimlib.search.model.NIMIndexException;
import com.netease.nimlib.search.model.NIMIndexRecord;

public final class LuceneReader {
	private final String TAG = "LuceneReader";

	private final String path;
	private Directory dir;
	private IndexReader reader;

	public LuceneReader(String path) {
		this.path = path;
	}

	public final void open() throws NIMIndexException {
		try {
			dir = FSDirectory.open(Paths.get(path));
			reader = DirectoryReader.open(dir);
		} catch (IOException e) {
			throw new NIMIndexException(e.getMessage());
		}
	}

	public final void close() {
		try {
			if (reader != null) {
				reader.close();
			}

			if (dir != null) {
				dir.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * ************************** queryAll ***************************
	 */
	public List<NIMIndexRecord> queryAll(long type, String keyword, int limit) throws NIMIndexException {
		if (limit <= 0) {
			limit = Integer.MAX_VALUE;
		}

		Sort sort = new Sort(new SortField(MsgDocFieldName.TIME, Type.LONG, true)); // 排序字段，按时间倒序
		GroupingSearch groupingSearch = new GroupingSearch(MsgDocFieldName.ID); // 按id分组
		groupingSearch.setGroupSort(sort); // 组与组之间的排序字段
		groupingSearch.setSortWithinGroup(sort); // 组内排序字段
		groupingSearch.setGroupDocsLimit(1); // 组内最多多少条
		groupingSearch.setFillSortFields(true);
		groupingSearch.setCachingInMB(16.0, true);

		List<NIMIndexRecord> records = new ArrayList<>();
		try {
			Query query = new QueryParser(MsgDocFieldName.CONTENT, new IKAnalyzer()).parse(keyword);

			IndexSearcher searcher = new IndexSearcher(reader);
			TopGroups<BytesRef> result = groupingSearch.search(searcher, query, 0, limit); // 最多limit个分组

			Log.i(TAG, "total hit count：" + result.totalHitCount);
			Log.i(TAG, "group count：" + result.groups.length);

			Document document;
			for (GroupDocs<BytesRef> groupDocs : result.groups) {
				Log.i(TAG, "group：" + groupDocs.groupValue.utf8ToString());
				Log.i(TAG, "total hit count in this group：" + groupDocs.totalHits);
				for (ScoreDoc scoreDoc : groupDocs.scoreDocs) {
					document = searcher.doc(scoreDoc.doc);
					records.add(MsgDocConverter.buildRecord(document, groupDocs.totalHits));
				}
			}

		} catch (ParseException e) {
			e.printStackTrace();
			throw new NIMIndexException(e.getMessage());
		} catch (IOException e) {
			e.printStackTrace();
			throw new NIMIndexException(e.getMessage());
		}

		return records;
	}

	/**
	 * *********************** querySession **************************
	 */

	/**
	 * 指定会话关键字查询（返回所有匹配记录），注意： 如果数据量大，容易OOM，且性能较差，建议使用分页查询。
	 */
	public List<NIMIndexRecord> querySession(long type, String keyword, String id, int limit) throws NIMIndexException {
		if (limit <= 0) {
			limit = Integer.MAX_VALUE;
		}

		List<NIMIndexRecord> result = null;
		BooleanQuery booleanQuery = buildSessionQuery(type, keyword, id);
		Sort sort = buildSessionQuerySort();
		try {
			// do search
			IndexSearcher searcher = new IndexSearcher(reader);
			TopDocs topDocs = searcher.search(booleanQuery, limit, sort);

			Log.i(TAG, "total match count=" + topDocs.totalHits);

			// build result
			result = new ArrayList<>(topDocs.totalHits);
			for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
				result.add(MsgDocConverter.buildRecord(searcher.doc(scoreDoc.doc), scoreDoc));
			}

			Log.i(TAG, "build record count=" + result.size());
		} catch (IOException e) {
			e.printStackTrace();
			throw new NIMIndexException(e.getMessage());
		}

		if (result != null) {
			for (NIMIndexRecord r : result) {
				Log.i(TAG, "match record:" + r.toString());
			}
		}

		return result;
	}

	/**
	 * 指定会话关键字查询（分页返回匹配记录）
	 */
	public List<NIMIndexRecord> querySessionPage(long type, String keyword, String id, int pageIndex, int pageSize)
			throws NIMIndexException {
		if (pageIndex <= 0) {
			pageIndex = 1;
		}

		if (pageSize <= 0) {
			pageSize = Integer.MAX_VALUE;
		}

		List<NIMIndexRecord> result = null;
		BooleanQuery booleanQuery = buildSessionQuery(type, keyword, id);
		Sort sort = buildSessionQuerySort();
		try {
			// do search
			IndexSearcher searcher = new IndexSearcher(reader);
			ScoreDoc lastSd = getLastScoreDoc(pageIndex, pageSize, booleanQuery, sort, searcher);
			TopDocs topDocs = searcher.searchAfter(lastSd, booleanQuery, pageSize, sort);

			// build result
			result = buildQueryResult(topDocs, searcher);
			Log.i(TAG, "build record count=" + result.size());
		} catch (IOException e) {
			e.printStackTrace();
			throw new NIMIndexException(e.getMessage());
		}

		return result;
	}

	/**
	 * 指定会话关键字查询（分页查询：根据锚点，返回下一页匹配记录）
	 */
	public List<NIMIndexRecord> querySessionNextPage(long type, String keyword, String id, NIMIndexRecord anchor,
			int pageSize) throws NIMIndexException {
		if (pageSize <= 0) {
			pageSize = Integer.MAX_VALUE;
		}

		List<NIMIndexRecord> result = null;
		BooleanQuery booleanQuery = buildSessionQuery(type, keyword, id);
		Sort sort = buildSessionQuerySort();
		ScoreDoc lastSd;
		if (anchor == null || anchor.scoreDoc == null) {
			lastSd = null;
		} else {
			lastSd = anchor.scoreDoc;
		}

		try {
			// do search
			IndexSearcher searcher = new IndexSearcher(reader);
			TopDocs topDocs = searcher.searchAfter(lastSd, booleanQuery, pageSize, sort);

			// build result
			result = buildQueryResult(topDocs, searcher);
			Log.i(TAG, "build record count=" + result.size());
		} catch (IOException e) {
			e.printStackTrace();
			throw new NIMIndexException(e.getMessage());
		}

		return result;
	}

	/**
	 * 指定会话关键字查询匹配记录总数
	 */
	public int querySessionMatchCount(long type, String keyword, String id) throws NIMIndexException {
		BooleanQuery booleanQuery = buildSessionQuery(type, keyword, id);
		int result = 0;
		try {
			// do search
			IndexSearcher searcher = new IndexSearcher(reader);
			TopDocs topDocs = searcher.search(booleanQuery, Integer.MAX_VALUE);
			result = topDocs.totalHits;
			Log.i(TAG, "total match count=" + topDocs.totalHits);
		} catch (IOException e) {
			e.printStackTrace();
			throw new NIMIndexException(e.getMessage());
		}

		return result;
	}

	/**
	 * 指定会话关键字查询匹配记录总页数
	 */
	public int querySessionPageCount(long type, String keyword, String id, int pageSize) throws NIMIndexException {
		if (pageSize <= 0) {
			pageSize = Integer.MAX_VALUE;
		}

		int totalCount = querySessionMatchCount(type, keyword, id);
		return (totalCount % pageSize == 0) ? totalCount / pageSize : totalCount / pageSize + 1;
	}

	private ScoreDoc getLastScoreDoc(int pageIndex, int pageSize, BooleanQuery query, Sort sort, IndexSearcher searcher)
			throws IOException {
		if (pageIndex == 1) {
			return null;
		}

		int limit = pageSize * (pageIndex - 1);
		TopDocs topDocs = searcher.search(query, limit, sort);

		if (limit > topDocs.totalHits) {
			limit = topDocs.totalHits; // over range
		}

		return topDocs.scoreDocs[limit - 1];
	}

	private BooleanQuery buildSessionQuery(long type, String keyword, String id) throws NIMIndexException {
		// 前置条件
		Query q1 = new TermQuery(new Term(MsgDocFieldName.TYPE, ((Long) type).toString()));
		Query q2 = new TermQuery(new Term(MsgDocFieldName.ID, id));

		// 中文采用IK分词器匹配
		Query q3;
		try {
			q3 = new QueryParser(MsgDocFieldName.CONTENT, new IKAnalyzer()).parse(keyword);
		} catch (ParseException pe) {
			pe.printStackTrace();
			throw new NIMIndexException(pe.getMessage());
		}

		// 英文及数字采用前缀匹配
		List<Query> q4 = new ArrayList<>();
		List<String> tokens = NIMTokenizer.getInstance().getEnglishNumberTokens(keyword);
		for (String token : tokens) {
			q4.add(new PrefixQuery(new Term(MsgDocFieldName.CONTENT, token)));
		}

		// 组合查询
		BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
		booleanQuery.add(q1, BooleanClause.Occur.MUST);
		booleanQuery.add(q2, BooleanClause.Occur.MUST);

		if (q4.isEmpty()) {
			booleanQuery.add(q3, BooleanClause.Occur.MUST);
		} else {
			// content match q3 or q4 list
			BooleanQuery.Builder contentQuery = new BooleanQuery.Builder();
			contentQuery.add(q3, BooleanClause.Occur.SHOULD); // OR
			for (Query q : q4) {
				contentQuery.add(q, BooleanClause.Occur.SHOULD); // OR
			}
			booleanQuery.add(contentQuery.build(), BooleanClause.Occur.MUST);
		}

		return booleanQuery.build();
	}

	private Sort buildSessionQuerySort() {
		return new Sort(SortField.FIELD_SCORE, new SortField(MsgDocFieldName.TIME, Type.LONG, true));
	}

	private List<NIMIndexRecord> buildQueryResult(final TopDocs topDocs, final IndexSearcher searcher)
			throws IOException {
		if (topDocs == null) {
			return null;
		}

		List<NIMIndexRecord> result = new ArrayList<>(topDocs.scoreDocs.length);
		for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
			result.add(MsgDocConverter.buildRecord(searcher.doc(scoreDoc.doc), scoreDoc));
		}
		return result;
	}
}